package com.zbangmall.pay.wechat;

import com.alibaba.fastjson.JSON;
import com.alipay.api.internal.util.StringUtils;
import com.zbangmall.common.HttpClientUtils;
import com.zbangmall.common.SnGenerator;
import com.zbangmall.pay.wechat.domain.*;
import com.zbangmall.pay.wechat.utils.WeChatPayErrorUtil;
import com.zbangmall.pay.wechat.utils.WechatPayConfigurations;
import com.zbangmall.pay.wechat.utils.WechatPayConst;
import com.zbangmall.util.common.MD5Util;
import com.zbangmall.util.convert.TypeConvert;
import com.zbangmall.util.convert.TypeExtract;
import org.apache.http.client.utils.URIBuilder;
import org.dom4j.Document;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.net.URI;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * 微信支付操作类
 */
public class WechatPayAssistant {

	/**
	 * 解析回调请求
	 */
	public static WechatPayRet parseRequest(HttpServletRequest request) throws Exception {
		String xml = TypeExtract.extXml(request);
		return TypeConvert.toObj(WechatPayRet.class, xml, TypeConvert.TypeEnum.XML);
	}

	/**
	 * 应答微信回调
	 */
	public static void echo(HttpServletResponse response) throws Exception {
		response.setContentType("application/xml");
		ServletOutputStream os = response.getOutputStream();
		os.print(echo());
	}

	/**
	 * 异步回调应答
	 */
	public static String echo() throws Exception {
		return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
	}

	/**
	 * 异步回调应答
	 */
	@Deprecated
	public static String responseTo() throws Exception {
		return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
	}

	/**
	 * 微信移动支付退款
	 */
	public static WechatRefundRet refund(WechatRefundInfo refundInfo) throws Exception {

		refundInfo.setAppid(WechatPayConfigurations.getAppId());
		refundInfo.setMch_id(WechatPayConfigurations.getMchId());
		refundInfo.setNonce_str(SnGenerator.randomMix(32));
		refundInfo.setSign(generateSign(refundInfo));
		TreeMap<String, Object> map = getSignMap(refundInfo, WechatRefundInfo.class);
		Document                doc = TypeConvert.toDoc(map);
		URI uri = new URIBuilder().setScheme("https").setHost("api.mch.weixin.qq.com").setPath("/secapi/pay/refund")
				.build();
		String xml = HttpClientUtils.connectWithXMLAndSSLByPost(uri, doc,
				WechatPayConfigurations.getRefundCertificatePath(),
				WechatPayConfigurations.getRefundCertificatePassword());
		WechatRefundRet obj = TypeConvert.toObj(WechatRefundRet.class, xml, TypeConvert.TypeEnum.XML);
		return obj;
	}

	/**
	 * 微信预支付订单
	 */
	public static WechatPreOrderInfo preOrder(WechatPayInfo payInfo) throws Exception {

		if (payInfo.getTrade_type().equals(WechatPayConst.TRADE_TYPE_OFFICIAL_ACCOUNT) && StringUtils.isEmpty(payInfo.getOpenid())) {
			throw new RuntimeException("公众号支付openid不能为空，请填入正确的openid");
		}

		payInfo.setAppid(WechatPayConfigurations.getAppId());
		payInfo.setMch_id(WechatPayConfigurations.getMchId());

		payInfo.setNonce_str(SnGenerator.randomMix(32).toUpperCase());
		payInfo.setSign(generateSign(payInfo));
		Document doc = TypeConvert.toDoc(getSignMap(payInfo, WechatPayInfo.class));
		URI uri = new URIBuilder().setScheme("https").setHost("api.mch.weixin.qq.com").setPath("/pay/unifiedorder")
				.build();
		String             xml  = HttpClientUtils.connectWithXMLByPost(uri, doc);
		WechatPreOrderInfo info = TypeConvert.toObj(WechatPreOrderInfo.class, xml, TypeConvert.TypeEnum.XML);
		if (!info.isContact()) {
			String msg = info.getReturn_code() + ":" + info.getReturn_msg();
			msg = new String(msg.getBytes("iso-8859-1"), "utf-8");
			throw new RuntimeException(msg);
		}
		if (!info.isSuccess()) {
			String msg = info.getResult_code() + ":" + WeChatPayErrorUtil.getErrorMsg(info.getErr_code());
			throw new RuntimeException(msg);
		}
		return info;
	}

	/**
	 * 设置签名
	 */
	private static String generateSign(Object obj) throws Exception {
		TreeMap<String, Object> map = getSignMap(obj, obj.getClass());
		return signMap(map);
	}


	/**
	 * 获取按顺序整理好的非空值字段
	 */
	private static TreeMap<String, Object> getSignMap(Object obj, Class<?> clz) throws Exception {
		if (obj == null) {
			throw new RuntimeException("支付对象不能为空");
		}
		Field[] fields = clz.getDeclaredFields();

		// 使用treeMap将字段按要求排序
		TreeMap<String, Object> map = new TreeMap<>();
		for (Field field : fields) {
			map.put(field.getName(), clz.getMethod("get" + uperFirst(field.getName())).invoke(obj));
		}

		// 使用fastjson过滤掉null的字段
		String json = JSON.toJSONString(map);
		map = JSON.parseObject(json, TreeMap.class);
		return map;
	}

	/**
	 * 首字母大写
	 */
	private static String uperFirst(String name) {
		return name.substring(0, 1).toUpperCase() + name.substring(1);
	}

	/**
	 * APP支付二次加签
	 */
	public static Map<String, Object> sign4App(WechatPreOrderInfo preOrderInfo) throws Exception {
		TreeMap<String, Object> map = new TreeMap<>();
		map.put("appid", preOrderInfo.getAppid());
		map.put("partnerid", preOrderInfo.getMch_id());
		map.put("prepayid", preOrderInfo.getPrepay_id());
		map.put("package", preOrderInfo.getPackage());
		map.put("noncestr", SnGenerator.randomMix(32));
		map.put("timestamp", preOrderInfo.getTimestamp());
		map.put("sign", signMap(map));
		return map;
	}

	/**
	 * 微信内H5支付二次加签(吐槽微信，APP加签字段全部小写，这里加签又驼峰，炒鸡不规范啊)
	 */
	public static Map<String, Object> sign4WechatOfficialAcc(WechatPreOrderInfo preOrderInfo) throws Exception {
		Long   timestamp = preOrderInfo.getTimestamp();
		String appId     = preOrderInfo.getAppid();
		String prepayId  = preOrderInfo.getPrepay_id();
		String packages  = "prepay_id=" + prepayId;
		String nonceStr  = SnGenerator.randomMix(32);

		SortedMap<String, Object> map = new TreeMap<String, Object>();
		map.put("appId", appId);
		map.put("timeStamp", timestamp);
		map.put("nonceStr", nonceStr);
		map.put("package", packages);
		map.put("signType", "MD5");

		String                    paySign = signMap(map);
		SortedMap<String, Object> mapSign = new TreeMap<>();
		mapSign.put("appId", appId);
		mapSign.put("prepay_id", prepayId);
		mapSign.put("timeStamp", timestamp);
		mapSign.put("nonceStr", nonceStr);
		mapSign.put("package", packages);
		mapSign.put("paySign", paySign);
		mapSign.put("signType", "MD5");
		return mapSign;
	}

	/**
	 * 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
	 */
	public static String signMap(SortedMap<String, Object> map) {
		StringBuffer sb = new StringBuffer();
		for (Map.Entry<String, Object> entry : map.entrySet()) {
			sb.append(entry.getKey() + "=" + entry.getValue() + "&");
		}
		sb.append("key=" + WechatPayConfigurations.getPayKey());
		return MD5Util.MD5Encode(sb.toString(), "UTF-8").toUpperCase();
	}
}
